package dungeonElements;

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.HashMap;

import temperature.Temperature;
import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;
import dungeon.Dungeon;
import dungeon.RoomHandler;
import dungeonTools.Direction;

/**
 * A class of squares. Each square describes part of the playing field in a RPG.
 * A square has a temperature, a humidity, can deal heat-, cold- and rust damage,
 * has a certain level of inhabitability, has at least one border and can be slippery.
 * 
 * @invar A square's current temperature is always valid
 * 		|canHaveAsTemperature(getTemperature())
 * @invar The humidity of a square is always a valid level
 * 		|isValidHumidity(getHumidity())
 * @invar A square has always proper borders. This means: 
 * 		-for each direction a square either doesn't have a border
 * 		 	or the border in turn has this square as bordering square.
 * 		-for each direction a square either doesn't have a border
 * 		 	or the border isn't terminated.
 * 		|hasProperBorders()
 * 
 * @author Tom Jacobs & Ewoud De Moor
 * @version 3.0
 */
public abstract class Square {

	/**
	 * The Default temperature new squares will have (25 degrees Celsius)
	 */
	public static final Temperature DEFAULT_TEMPERATURE =
										new Temperature(BigDecimal.valueOf(25));
	
	/**
	 * The Default humidity new squares will have (35.00%)
	 */
	public static final double DEFAULT_HUMIDITY = 35.00d;
		
	/**
	 * Creates a new square with the following attributes:
	 * Temperature - see param temperature
	 * Humidity - see param humidity
	 * Borders - see param northBorder, eastBorder, southBorder, 
	 * 						westBorder, ceilingBorder and floorBorder
	 * Slippery Surface - DEFAULT: false
	 * Terminated - DEFAULT: false
	 * 
	 * @param temperature
	 * 		The temperature this square will have.
	 * @param humidity
	 * 		The humidity level this square will have.
	 * @param northBorder
	 * 		Determines whether this square has a wall in the northern direction.
	 * @param eastBorder
	 * 		Determines whether this square has a wall in the eastern direction.
	 * @param southBorder
	 * 		Determines whether this square has a wall in the southern direction.
	 * @param westBorder
	 * 		Determines whether this square has a wall in the western direction.
	 * @param ceilingBorder
	 * 		Determines whether this square has a wall in the ceiling.
	 * @param floorBorder
	 * 		Determines whether this square has a wall in the floor.
	 * @post This new square has no slippery surface.
	 * 		|!new.hasSlipperySurface()
	 * @post This new square is not yet terminated.
	 * 		|!new.isTerminated()
	 * @effect The temperature of this square will be set to param temperature
	 * 		|setTemperature(temperature)
	 * @effect The humidity level of this square will be set to param humidity
	 * 		|setHumidity(humidity)
	 * @effect A wall is build in the northern direction if northBorder is true.
	 * 		|if(northBorder)
	 * 		|	then buildWall(Direction.NORTH)
	 * @effect A wall is build in the eastern direction if eastBorder is true.
	 * 		|if(eastBorder)
	 * 		|	then buildWall(Direction.EAST)
	 * @effect A wall is build in the southern direction if southBorder is true.
	 * 		|if(southBorder)
	 * 		|	then buildWall(Direction.SOUTH)
	 * @effect A wall is build in the western direction if westBorder is true.
	 * 		|if(westBorder)
	 * 		|	then buildWall(Direction.WEST)
	 * @effect A wall is build in the ceiling if ceilingBorder is true.
	 * 		|if(ceilingBorder)
	 * 		|	then buildWall(Direction.CEILING)
	 * @effect A wall is build in the floor if floorBorder is true.
	 * 		|if(floorBorder)
	 * 		|	then buildWall(Direction.FLOOR)
	 * @effect The surface of this square will be set to not slippery.
	 * 		|setSlipperySurface(false)
	 * @throws IllegalArgumentException
	 * 		Borders must be set properly.
	 * 		| !(new.hasProperBorders())
	 */
	@Raw
	public Square(Temperature temperature, double humidity, 
			boolean northBorder, boolean eastBorder, boolean southBorder, 
			boolean westBorder, boolean ceilingBorder, boolean floorBorder)
		throws IllegalArgumentException
	{
		if(!isValidHumidity(humidity))
			throw new IllegalArgumentException("Invalid humidity value");
		this.humidity = humidity;
		
		if(!canHaveAsTemperature(temperature))
			throw new IllegalArgumentException("Invalid temperature value");
		this.temperature = temperature;
		
		borders.put(Direction.NORTH, null);
		borders.put(Direction.EAST, null);
		borders.put(Direction.SOUTH, null);
		borders.put(Direction.WEST, null);
		borders.put(Direction.CEILING, null);
		borders.put(Direction.FLOOR, null);
		
		if(northBorder){
			buildWall(Direction.NORTH);
		}
		if(eastBorder){
			buildWall(Direction.EAST);
		}
		if(southBorder){
			buildWall(Direction.SOUTH);
		}
		if(westBorder){
			buildWall(Direction.WEST);
		}
		if(ceilingBorder){
			buildWall(Direction.CEILING);
		}
		if(floorBorder){
			buildWall(Direction.FLOOR);
		}
		
		if(!hasProperBorders()){
			this.terminate();
			throw new IllegalArgumentException("Borders not proper set"); 
			
		}
	}
	
	/**
	 * Creates a new default square with the following attributes:
	 * Temperature - the DEFAULT_TEMPERATURE value
	 * Humidity - the Temperature.DEFAULT_HUMIDITY value
	 * Borders - DEFAULT: Walls in all directions
	 * Slippery Surface - DEFAULT: false
	 * Terminated - DEFAULT: false
	 * 
	 * @effect This square is initialized with the default properties.
	 * 		| this(DEFAULT_TEMPERATURE, DEFAULT_HUMIDITY,true,true,true,true,true,true)
	 */
	@Raw
	public Square(){
		this(DEFAULT_TEMPERATURE, DEFAULT_HUMIDITY,true,true,true,true,true,true);
	}
	
	
	/**
	 * Terminate this square. This removes the borders in all directions 
	 * and sets the square to terminated.
	 * 
	 * @post This square is terminated.
	 * 		| new.isTerminated()
	 * @post If this square was not already terminated, this square no longer references. 
	 * 		border as bordering squares.
	 * 		| if(!isTerminated())
	 * 		|	for each direction in Direction:
	 * 		|		!hasBorderInDirection(directions)
	 * @post If this square was not already terminated, the borders no longer reference 
	 * 		this square as there bordering square.
	 * 		| if(!isTerminated())
	 * 		|	for each direction in Direction:
	 * 		|		(new old.getBorder(direction))
	 * 		|			.hasSquareInDirection(direction.getOppositeDirection) == false 
	 * @effect Removes the square from the dungeon it's in
	 * 		| if(Dungeon.isSquareInsideAnyDungeon(this))
	 * 		|	Dungeon.remove(this);
	 */
	public void terminate() {
		if(!isTerminated()){
			this.isTerminated = true;
			for(Direction allDirections : Direction.values() ){
				if(this.hasBorderInDirection(allDirections)){
					setBorder(null, allDirections);
				}
			}
			if(Dungeon.isSquareInsideAnyDungeon(this))
				Dungeon.remove(this);
		}
	}
	
	/**
	 * Check whether this square is terminated.
	 */
	@Basic @Raw
	public boolean isTerminated() {
		return this.isTerminated;
	}
	
	/**
	 * Variable registering whether this square is terminated.
	 */
	private boolean isTerminated = false;
	

	/**
	 * Return the temperature of this square.
	 */
	@Basic @Raw
	public Temperature getTemperature() {
		return this.temperature;
	}
	
	/** 
	 * Returns the Cold Damage the square will cause to creatures.
	 * 
	 * @return The Cold Damage done to creatures by this square.
	 */
	public int getColdDamage() {
		Temperature minFiveDegreeCelsius = new Temperature(BigDecimal.valueOf(-5));
		if( getTemperature().toCelsius().compareTo(minFiveDegreeCelsius) < 0 ){
			BigDecimal bigResult = getTemperature().toCelsius().getNumeral();
			bigResult = bigResult.negate().subtract(BigDecimal.valueOf(5))
						.divide(BigDecimal.valueOf(10), 1, BigDecimal.ROUND_DOWN);
						// (-x-5)/10
			if(bigResult.compareTo(BigDecimal.valueOf(Integer.MAX_VALUE))>=0 ){
				return Integer.MAX_VALUE;
			}
			return bigResult.intValue();
		}
		return 0;
	}
	
	/** 
	 * Returns the Heat Damage the square will cause to creatures.
	 * 
	 * @return The Heat Damage done to creatures by this square.
	 */
	public int getHeatDamage() {
		Temperature thirtyFiveDegreeCelsius = new Temperature(BigDecimal.valueOf(getHeatDamageThreshold()));
		if( getTemperature().toCelsius().compareTo(thirtyFiveDegreeCelsius) > 0 ){
			BigDecimal bigResult = getTemperature().toCelsius().getNumeral();
			bigResult = bigResult.subtract(BigDecimal.valueOf(getHeatDamageThreshold()))
						.divide(BigDecimal.valueOf(getHeatDamageFactor()), 1, BigDecimal.ROUND_DOWN);
						// (x-threshold)/factor
			if(bigResult.compareTo(BigDecimal.valueOf(Integer.MAX_VALUE))>=0 ){
				return Integer.MAX_VALUE;
			}
			return bigResult.intValue();
		}
		return 0;
	}
	
	/**
	 * Returns the heat damage threshold value currently set for this square.
	 * This is the minimum value for the temperature above which
	 * Heat Damage will be dealt.
	 * 
	 * @return the threshold for Heat Damage
	 */
	@Basic @Raw
	public static int getHeatDamageThreshold() {
		return heatDamageThreshold;
	}

	/**
	 * the heat damage threshold value currently set for this square.
	 * This is the minimum value for the temperature above which
	 * Heat Damage will be dealt.
	 * Can not be lower than the threshold for cold damage (-5 \u00B0C)
	 * 
	 * @param threshold The threshold to set for Heat Damage
	 * @post the new threshold is equal to the parameter
	 * 			| threshold == Square.getHeatDamageThreshold()
	 * @throws	IllegalArgumentException
	 * 			Threshold must be greater than -5.
	 * 			| threshold <= -5
	 */
	@Raw
	public static void setHeatDamageThreshold(int threshold) throws IllegalArgumentException {
		if(threshold <= -5)
			throw new IllegalArgumentException("Threshold must be greater than -5");
		Square.heatDamageThreshold = threshold;
	}

	/**
	 * threshold for Heat Damage
	 */
	private static int heatDamageThreshold = 35;
	
	/**
	 * Returns the temperature factor for calculating Heat Damage.
	 * 
	 * @return The factor for Heat Damage
	 */
	@Basic @Raw
	public static int getHeatDamageFactor() {
		return heatDamageFactor;
	}

	/**
	 * Sets the factor for calculating Heat Damage
	 * 
	 * @param 	factor The factor to set for Heat Damage
	 * @post the new factor is equal to the parameter
	 * 		| factor == Square.getHeatDamageFactor()
	 * @throws	IllegalArgumentException
	 * 		Factor must be positive.
	 * 		| factor <= 0
	 */
	@Raw
	public static void setHeatDamageFactor(int factor) throws IllegalArgumentException {
		if(factor <= 0)
			throw new IllegalArgumentException("factor must be positive");
		Square.heatDamageFactor = factor;
	}
	
	/**
	 * factor for Heat Damage
	 */
	private static int heatDamageFactor = 15;
	
	/**
	 * Check whether this square can have the given temperature as its temperature.
	 * 
	 * @param 	temperature
	 * 			The temperature to check.
	 * @return 	False if the given temperature isn't a possible
	 * 			temperature for this square. The new temperature should be 
	 * 			between the maximum and minimum temperature of this square.
	 * 			| result ==
	 * 			|	( this.matchesTemperatureMaxTemperature(temperature) 
	 * 			|		&& this.matchesTemperatureMinTemperature(temperature) )
	 * @throws IllegalArgumentException
	 * 			temperature must be effective
	 * 			|temperature == null
	 */
	@Raw
	public boolean canHaveAsTemperature(Temperature temperature) throws IllegalArgumentException{
		if(temperature == null)
			throw new IllegalArgumentException("Non-effective temperature");
		if(!( this.matchesTemperatureMaxTemperature(temperature) 
				&& this.matchesTemperatureMinTemperature(temperature) ))
			return false;
		return true;
	}
	
	/**
	 * Set the temperature for this square to the given temperature.
	 * 
	 * @param 	temperature
	 * 			The new temperature for this square.
	 * @post 	The new temperature for this square is equal to the given 
	 * 			temperature.
	 * 		| new.getTemperature().compareTo(temperature) == 0
	 * @effect If this isn't a rock the temperature of bordering squares is updated.
	 * 		| if(!(this instanceof Rock))
	 * 		|	for each direction in Direction
	 * 		|		if( hasBorderInDirection(direction) 
	 * 		|		&& getBorder(direction).hasSquareInDirection(direction) 
	 * 		|		&& (getBorder(direction).getSquareInDirection(direction) instanceof Rock) )
	 * 		|			((Rock)getBorder(direction).getSquareInDirection(direction)).setTemperature()
	 * @effect Refreshes the data of all squares in this room
	 * 		|if(Dungeon.isSquareInsideAnyDungeon(this))
	 * 		|	RoomHandler.refreshEnclosingRoomData(this);
	 * @throws 	IllegalArgumentException
	 * 			This square cannot have the given temperature as its temperature.
	 * 			| ! canHaveAsTemperature(temperature)
	 */
	@Raw 
	public void setTemperature(Temperature temperature) 
		throws IllegalArgumentException{
		if (! canHaveAsTemperature(temperature))
			throw new IllegalArgumentException("Not a valid temperature");
		this.temperature = temperature;
		
		for(Direction direction: Direction.values()){
			if( !(this instanceof Rock) && hasBorderInDirection(direction) 
					&& getBorder(direction).hasSquareInDirection(direction) 
					&& (getBorder(direction).getSquareInDirection(direction) instanceof Rock) ){
				Rock rock = (Rock) getBorder(direction).getSquareInDirection(direction);
				rock.setTemperature();
			}
		}
		
		if(Dungeon.isSquareInsideAnyDungeon(this))
			RoomHandler.refreshEnclosingRoomData(this);
	}
	
	/**
	 * Variable registering the temperature of this square in degree Celsius.
	 */
	private Temperature temperature = Temperature.CELSIUS_0;
		
	/**
	 * Return the maximum temperature that applies to this square
	 * in degrees Celsius
	 */
	@Basic @Raw
	public Temperature getMaxTemperature(){
		return maxTemperature.toCelsius();
	}
	
	/**
	 * Check whether the given temperature exceeds the maximum temperature.
	 * 
	 * @param 	temperature
	 * 			The temperature to check against.
	 * @return	False if and only if the given temperature exceeds
	 * 			the maximum temperature.
	 * 			| result == (temperature.compareTo(getMaxTemperature()) <=0)
	 * @throws IllegalArgumentException
	 * 			The temperature must be effective
	 * 			|temperature == null
	 */
	public boolean matchesTemperatureMaxTemperature(Temperature temperature) 
		throws IllegalArgumentException{
		if(temperature == null)
			throw new IllegalArgumentException("Temperature not effective");
		return temperature.toCelsius().compareTo(getMaxTemperature()) <= 0;
	}
	
	/**
	 * Set the maximum temperature of this square to the given temperature.
	 * 
	 * @param 	temperature
	 * 			The new maximum temperature for this square.
	 * @effect	The new maximum temperature for this square is equal 
	 * 			to the given temperature.
	 * 			| new.getMaxTemperature().compareTo(temperature)
	 * @throws 	IllegalArgumentException
	 * 			A square cannot have a temperature less than -273.15 degrees Celsius as 
	 * 			its maximum temperature.
	 * 			| temperature.toCelsius().compareTo(Temperature.ABSOLUTE_ZERO)<0
	 * @throws 	IllegalArgumentException
	 * 			A square cannot have a temperature less than 
	 * 			the minimum temperature as its maximum temperature.
	 * 			| temperature.toCelsius().compareTo(getMinTemperature())<0
	 * @throws IllegalArgumentException
	 * 			The temperature must be effective
	 * 			| temperature == null
	 */
	@Raw 
	public void setMaxTemperature(Temperature temperature)
		throws IllegalArgumentException{
		if(temperature == null)
			throw new IllegalArgumentException();
		if(temperature.toCelsius().compareTo(Temperature.ABSOLUTE_ZERO)<0)
			throw new IllegalArgumentException();
		if(temperature.toCelsius().compareTo(getMinTemperature())<0)
			throw new IllegalArgumentException();
		maxTemperature = temperature;
	}
	
	/**
	 * Variable registering the maximum temperature in degree Celsius.
	 */
	private Temperature maxTemperature = new Temperature(BigDecimal.valueOf(5000)); 
	
	/**
	 * Return the minimum temperature that applies to this square
	 * in degree Celsius.
	 */
	@Basic
	public Temperature getMinTemperature(){
		return minTemperature.toCelsius();
	}
	
	/**
	 * Check whether the given temperature is below the minimum temperature.
	 * 
	 * @param 	temperature
	 * 			The temperature to check.
	 * @return	False if and only if the given temperature is below
	 * 			the minimum temperature.
	 * 			| result == (temperature.toCelsius().compareTo(getMinTemperature()) >= 0)
	 * @throws IllegalArgumentException
	 * 			The temperature must be effective
	 * 			|temperature == null
	 */
	public boolean matchesTemperatureMinTemperature(Temperature temperature)
		throws IllegalArgumentException{
		if(temperature == null)
			throw new IllegalArgumentException("Temperature not effective");
		return temperature.toCelsius().compareTo(getMinTemperature()) >= 0;
	}
	
	/**
	 * Set the minimum temperature of this square to the given temperature.
	 * 
	 * @param 	temperature
	 * 			The new minimum temperature for this square.
	 * @post	The new minimum temperature for this square is equal 
	 * 			to the given temperature.
	 * 			| new.getMinTemperature().compareTo(temperature) == 0
	 * @throws 	IllegalArgumentException
	 * 			A square cannot have a temperature less than -273 degree Celsius as 
	 * 			its minimum Temperature.
	 * 			| temperature.toCelsius().compareTo(Temperature.ABSOLUTE_ZERO)<0
	 * @throws 	IllegalArgumentException
	 * 			A square cannot have a temperature greater than 
	 * 			the maximum temperature as its minimum temperature.
	 * 			| temperature.toCelsius().compareTo(getMaxTemperature())>0
	 * @throws IllegalArgumentException
	 * 			The temperature must be effective
	 * 			| temperature == null
	 */
	@Raw 
	public void setMinTemperature(Temperature temperature)
	throws IllegalArgumentException{
		if(temperature == null)
			throw new IllegalArgumentException();
		if(temperature.toCelsius().compareTo(Temperature.ABSOLUTE_ZERO)<0)
			throw new IllegalArgumentException();
		if(temperature.toCelsius().compareTo(getMaxTemperature())>0)
			throw new IllegalArgumentException();
		minTemperature = temperature;
	}
	
	/**
	 * Variable registering the minimum temperature in degree Celsius.
	 */
	private Temperature minTemperature = new Temperature(BigDecimal.valueOf(-200));

	
	/**
	 * Return the border of this square in the given direction. 
	 * If there is nothing in the given direction, the method will return null.
	 * 
	 * @param direction
	 * 		The direction for which the border will be returned.
	 * @throws IllegalArgumentException
	 * 		The direction must be effective
	 * 		| direction == null
	 */
	@Basic @Raw
	public Border getBorder(Direction direction) 
		throws IllegalArgumentException, IllegalStateException
	{
		if(direction == null)
			throw new IllegalArgumentException("Direction not initialized");
		if(!borders.containsKey(direction))
			throw new IllegalStateException("Direction not found");
		return borders.get(direction);
	}
	
	/**
	 * Check whether this square has a border in the given direction.
	 * 
	 * @param direction
	 * 		The direction to check.
	 * @return True if and only if this square has a border in the given direction.
	 * 		| result = getBorder(direction) != null
	 * @throws IllegalArgumentException
	 * 		direction must be initialized.
	 * 		| direction == null
	 */
	public boolean hasBorderInDirection(Direction direction) 
		throws IllegalArgumentException, IllegalStateException
	{
		if(direction == null)
			throw new IllegalArgumentException("Direction not initialized");
		if(!borders.containsKey(direction))
			throw new IllegalStateException("Direction not found");
		return borders.get(direction) != null;
	}
	
	/**
	 * Check whether this square can have the given border in the given direction 
	 * as its border.
	 * 
	 * @param border
	 * 		The border to check
	 * @param direction
	 * 		The direction to check
	 * @return False if direction is not initialized
	 * 		| if(direction == null)
	 * 		|	then result == false
	 * @return True if this square is terminated and the border is set to nothing.
	 * 		| if(this.isTerminated && (border==null))
	 * 		|	then result == true
	 * @return False if the given border is a terminated border
	 * 		| if( (border!=null) && border.isTerminated())
	 * 		|	then result == false
	 * @return True if setting the border wouldn't change anything.
	 * 		| if( (getBorder(direction) != null && getBorder(direction).equals(border))
	 *		|		|| ((getBorder(direction)==null)&&(border==null)) )
	 * 		|	then result == true
	 */
	@Raw
	public boolean canHaveAsBorderInDirection(@Raw Border border, Direction direction){
		if(direction == null)
			return false; //Direction not initialized
		if( this.isTerminated() && (border==null) )
			return true;
		if( (border!=null) && border.isTerminated())
			return false; //Can not have a terminated border
		if( (getBorder(direction) != null && getBorder(direction).equals(border))
				|| ((getBorder(direction)==null)&&(border==null)) )
			return true; //Current state
		return true;
	}
	
	/**
	 * Check whether this square has proper borders.
	 * 
	 * @return False if in some direction this square has an border that isn't proper set 
	 * 		or is terminated.
	 * 		| for each direction in Direction:
	 *		|	if( (borders.get(direction) != null) 
	 *		|		&& (borders.get(direction).getSquareInDirection(direction.getOppositeDirection()) != this) )
	 *		|	then result == false
	 * 		|	if( (borders.get(direction) != null) && (borders.get(direction).isTerminated()) )
	 * 		|		then result == false
	 */
	@Raw
	public boolean hasProperBorders(){
		for(Direction allDirections : Direction.values()){
			if( !hasProperBorderInDirection(allDirections))
				return false;
		}
		return true;	
	}
	
	/**
	 * Checks whether or not the square has a proper border in a specific
	 * direction.
	 * 
	 * @param direction
	 *			The direction in which to check 			
	 * @return 	False if there is no border in that direction
	 * 			|if( !borders.containsKey(direction) ) 
	 * 			|	result == false
	 * @return	False if the border doesn't have this square set correctly.
	 *			|if( (borders.get(direction) != null) 
	 *			|		&& (borders.get(direction).getSquareInDirection(direction.getOppositeDirection()) != this) )
	 *			|	then result == false
	 * @return	False if the border in the given direction is terminated.
	 * 			|if( (borders.get(direction) != null) && (borders.get(direction).isTerminated()) )
	 * 			|	then result == false
	 */
	protected boolean hasProperBorderInDirection(Direction direction) {
		if( !borders.containsKey(direction) ) 
			return false; //Direction not initialized
		if( (borders.get(direction) != null) 
				&& (borders.get(direction).getSquareInDirection(direction.getOppositeDirection()) != this) )
			return false; //Check back-reference
		if( (borders.get(direction) != null) && (borders.get(direction).isTerminated()) )
			return false; //border can't be terminated
		return true;
	}

	/**
	 * Set the border in a given direction to the given border.
	 * 
	 * @param border
	 * 		The border to set.
	 * @param direction
	 * 		The direction in which the given border must be set.
	 * @post This square has the given border as its border in the given direction.
	 * 		| new.getBorder(direction) == border
	 * @post The given border reference this square as its bordering square in 
	 * 		the given direction.
	 * 		| border.getSquareInDirection(direction.getOppositeDirection())==this
	 * @post The former border does not reference this square as its bordering square.
	 * 		| if(old.hasBorder(direction))
	 * 		|	(new old.getBorder(direction))
	 * 		|		.getSquareInDirection(direction.getOppositeDirection())!=this
	 * @effect If the new border is a wall with at least one bordering rock, 
	 * 		the temperature of the rocks is updated.
	 * 		| if( border instanceof Wall)
	 * 		|	if(border.hasSquareInDirection(direction) 
	 * 		|			&& (border.getSquareInDirection(direction) instanceof Rock) )
	 * 		|		((Rock)border.getSquareInDirection(direction)).setTemperature()
	 * 		|	if(border.hasSquareInDirection(direction.getOppositeDirection()) 
	 * 		|			&& (border.getSquareInDirection(direction.getOppositeDirection()) instanceof Rock) )
	 * 		|		((Rock)border.getSquareInDirection(direction.getOppositeDirection())).setTemperature()
	 * @throws IllegalArgumentException
	 * 		Setting the given border must result in a valid square.
	 * 		| !canHaveAsBorderInDirection(border, direction)
	 */
	@Raw
	public void setBorder(@Raw Border border, Direction direction)
		throws IllegalArgumentException
	{
		if( !canHaveAsBorderInDirection(border, direction) )
				throw new IllegalArgumentException("This square can not have the given border in the given direction");
		if( (getBorder(direction) != null && getBorder(direction).equals(border))
				|| ((getBorder(direction)==null)&&(border==null)) )
			return; //Current state
		Border formerBorder = getBorder(direction);
		borders.put(direction, border);
		if(border!=null) 
			border.setSquareInDirection(this, direction.getOppositeDirection());
		if( (border==null) && (formerBorder!=null) ){ 
			if( (this.isTerminated)&&(formerBorder.canRemoveSquareInDirection(direction.getOppositeDirection())) ){
				//remove this square in formerBorder 
				formerBorder.removeSquareInDirection(direction.getOppositeDirection());
			} else if(formerBorder.hasSquareInDirection(direction.getOppositeDirection())) { 
				formerBorder.terminate(); //This border is no longer used
			}
		}
		if( (border!=null) && (formerBorder!=null) ){ 
			if(formerBorder.hasSquareInDirection(direction)){
				//set bordering square to reference this border
				Square squareOnOtherSideOfFormerBorder = formerBorder.getSquareInDirection(direction);
				squareOnOtherSideOfFormerBorder.setBorder(border, direction.getOppositeDirection());
			}
			formerBorder.terminate();
		}	
		RoomHandler.setBorder(this,border,direction);
		//If one of the borderings squares is a Rock, set the temperature of that rock correct: 
		if( border instanceof Wall){
			if(border.hasSquareInDirection(direction) 
					&& (border.getSquareInDirection(direction) instanceof Rock) )
				((Rock)border.getSquareInDirection(direction)).setTemperature();
			if(border.hasSquareInDirection(direction.getOppositeDirection()) 
					&& (border.getSquareInDirection(direction.getOppositeDirection()) instanceof Rock) )
				((Rock)border.getSquareInDirection(direction.getOppositeDirection())).setTemperature();
		}
	}
	
	/**
	 * Removes the border in a given direction
	 * 
	 * @param direction
	 * 		The direction in which nothing must be set.
	 * @post This square will have no border in the given direction.
	 * 		| getBorder(direction)==null
	 * @throws IllegalArgumentException
	 * 		Setting the given border must result in a valid square.
	 * 		| !canHaveAsBorderInDirection(border, direction)
	 */
	public void breakBorder(Direction direction)
		throws IllegalArgumentException
	{
		if(!canHaveAsBorderInDirection(null, direction))
			throw new IllegalArgumentException("This square can not break border in that direction");
		if(getBorder(direction) == null)
			return;
		setBorder(null, direction);
	}
	
	/**
	 * Builds a wall in the given direction. Nothing happens if there's already one.
	 * If this square is slippery, so will the wall be.
	 * 
	 * @param direction
	 * 		The direction in which the wall must be set.
	 * @post This square will have a wall in the given direction.
	 * 		| getBorder(direction) instanceof Wall
	 * @post If there isn't already a wall, the new wall will be slippery if this 
	 * 		square is slippery.
	 * 		| new.getBorder(direction).hasSlipperySurface() == hasSlipperySurface()
	 * @throws IllegalArgumentException
	 * 		direction must be initialized.
	 * 		| direction == null
	 * @throws IllegalArgumentException
	 * 		Setting the given border must result in a valid square;
	 * 		| !canHaveAsBorderInDirection(border, direction)
	 */
	public void buildWall(Direction direction)
		throws IllegalArgumentException
	{
		if(direction == null)
			throw new IllegalArgumentException("Direction not initialized");
		Border border = new Wall(this, direction.getOppositeDirection(), hasSlipperySurface());
		if(!canHaveAsBorderInDirection(border, direction))
			throw new IllegalArgumentException("This square can not build a wall in that direction");
		if( (getBorder(direction).getSquareInDirection(direction.getOppositeDirection())==this) 
				&& (getBorder(direction) instanceof Wall) )
			return;	
		setBorder(border, direction);
	}
	
	/**
	 * Builds a door in the direction. If there's already a door
	 * in the direction, nothing will happen.
	 * 
	 * @param direction
	 * 		The direction in which the door must be set.
	 * @post This square will have a door in the given direction.
	 * 		| getBorder(direction) instanceof Door
	 * @throws IllegalArgumentException
	 * 		direction must be initialized.
	 * 		| direction == null
	 * @throws IllegalArgumentException
	 * 		Setting the given border must result in a valid square.
	 * 		| !canHaveAsBorderInDirection(border, direction)
	 */
	public void buildDoor(Direction direction)
		throws IllegalArgumentException
	{
		if(direction == null)
			throw new IllegalArgumentException("Direction not initialized");
		Door border = new Door(this, direction.getOppositeDirection(), true);
		if(!canHaveAsBorderInDirection(border, direction))
			throw new IllegalArgumentException("This square can not build a door in that direction");
		if( (getBorder(direction).getSquareInDirection(direction.getOppositeDirection())==this) 
				&& (getBorder(direction) instanceof Door) )
			return;	
		setBorder(border, direction);
	}
	
	/**
	 * Variable referencing the borders of this square. It maps a direction onto a border.
	 */
	private final HashMap<Direction, Border> borders = new HashMap<Direction, Border>();
	

	/**
	 * Returns the current humidity level at this square
	 * 
	 * @return The current humidity level at this square
	 */
	@Basic @Raw
	public double getHumidity(){
		return this.humidity;
	}
	
	/** 
	 * Returns the Rust Damage the square will cause to metal objects
	 * 
	 * @return The Rust Damage done to metal objects by this square
	 * 		|if(getHumidity() > 30)
	 *		|	then return ( ( (int)getHumidity() - 30) / 7);
	 *		|else return 0;
	 */
	@Raw
	public int getRustDamage() {
		if(getHumidity() > 30)
			return ( ( (int)getHumidity() - 30) / 7);
		return 0;
	}
	
	/**
	 * Returns whether or not the square causes Rust Damage to metal objects
	 * 
	 * @return True if the square will cause Rust Damage
	 * 		|if( getRustDamage() > 0)
	 * 		|	then result == true
	 * 		Otherwise, returns false
	 * 		|else result == false
	 */
	@Raw
	public boolean causesRustDamage(){
		return (getRustDamage() > 0);
	}
	
	/**
	 * Checks if the entered humidity is valid
	 * -Valid values are between 0 and 100
	 * -Valid values are rounded down to 2 decimals after the point
	 * 
	 * @param humidity 
	 * 		The humidity value to be checked
	 * @return True if the value is negative or above 100
	 * 		|if( (humidity >= 0) && (humidity <= 100) )
	 * 		|	then result == true
	 * 		True if the humidity isn't formatted as xxx.xx
	 * 		|if( Math.abs(roundHumidity(humidity)-humidity) < 0.0001)
	 * 		|	then result == true
	 * 		False in all other cases
	 * 		|else result == false
	 */
	@Raw
	public static boolean isValidHumidity(double humidity){
		if(humidity < 0)
			return false;
		if(humidity > 100)
			return false;
		double roundedHumidity = roundHumidity(humidity);
		if(Math.abs(roundedHumidity-humidity) >= 0.0001)
			return false;
		return true;
	}
	
	/**
	 * Checks if this Square can have the given humidity.
	 * 
	 * @param humidity 
	 * 		The humidity value to be checked
	 * @return False if the given humidity isn't valid.
	 * 		| if(!isValidHumidity(humidity))
	 * 		|	then result == false
	 */
	public boolean canHaveAsHumidity(double humidity){
		if(!isValidHumidity(humidity))
			return false;
		return true;
	}
	
	/**
	 * Rounds the humidity down to a value with only 2 decimals
	 * after the point
	 * 
	 * @param humidity
	 * 		The value to be rounded
	 * @return The humidity value entered, but rounded down towards
	 * 		negative infinity on 2 decimals after the point
	 */
	protected static double roundHumidity(double humidity){
		int formattingHumidity = (int) (humidity * 100);
		double rounded = (double)(formattingHumidity / 100d);
		return rounded;
	}

	/**
	 * A formatting function to convert humidity values from 
	 * doubles to strings formatted as xxx.xx%
	 * 
	 * @param humidity The value to be formatted
	 * @return A formatted string of the original double with
	 * an appended percentage-sign
	 */
	protected String formatHumidityPercentage(double humidity){
		NumberFormat nf = NumberFormat.getNumberInstance();
		nf.setGroupingUsed(false);
		nf.setMaximumFractionDigits(5);
		nf.setMinimumFractionDigits(2);
		return (nf.format(humidity) + "%");
	}

	/**
	 * Sets the new humidity value of the square
	 * 
	 * @param humidity 
	 * 		The new value that should be set
	 * @Pre The humidity must be a valid value.
	 * 		|isValidHumity(humidity)
	 * @Post The new humidity value of this square is set to
	 * 		the parameter value entered.
	 * 		|new.getHumidity() == humidity
	 * @effect	Refreshes the data of all squares in this room
	 * 		|if(Dungeon.isSquareInsideAnyDungeon(this))
	 * 		|	RoomHandler.refreshEnclosingRoomData(this);
	 */
	public void setHumidity(double humidity){
		assert isValidHumidity(humidity);
		assert canHaveAsHumidity(humidity);
		this.humidity = humidity;
		if(Dungeon.isSquareInsideAnyDungeon(this))
			RoomHandler.refreshEnclosingRoomData(this);
	}
	
	/**
	 * Variable registering the Humidity level of the square.
	 */
	private double humidity;
	
	
	/**
	 * Sets the surface of the square to slippery or not
	 * 
	 * @param slipperySurface
	 * 		The boolean value saying whether or not the
	 * 		surface of the square is slippery
	 * @post The new state of the surface is set to the boolean value
	 * 		|new.hasSlipperySurface() == slipperySurface
	 * @effect Sets the slippery state of this square inside its dungeon
	 * 		|if(Dungeon.isSquareInsideAnyDungeon(this))
	 * 		|	Dungeon.squareChangedSlipperySurfaceState(this);
	 */
	public void setSlipperySurface(boolean slipperySurface){
		this.slipperySurface = slipperySurface;
		if(Dungeon.isSquareInsideAnyDungeon(this))
			Dungeon.squareChangedSlipperySurfaceState(this);
	}

	/**
	 * Returns whether or not the surface of the square is 
	 * inherently slippery (made of a slippery material)
	 * 
	 * @return Whether or not the surface itself is slippery
	 */
	@Basic
	public boolean hasSlipperySurface() {
		return slipperySurface;
	}

	/**
	 * Returns whether or not this square is Slippery, on one of
	 * these conditions:
	 * - The humidity is at 100.00% and the temperature is above
	 * 0 degrees Celsius
	 * - The humidity is at 10.00% or higher and the temperature
	 * is 0 or negative
	 * - The surface of the square is inherently slippery
	 * 
	 * @return True if this square is slippery due to condensation
	 *		|if((getHumidity() == 100) && (getTemperature() > 0))
	 *		|	then result == true
	 *		True if this square is slippery due to ice
	 *		|if((getHumidity() >= 10) && (getTemperature() <= 0))
	 *		|	then result == true
	 *		True if the surface is inherently slippery
	 *		|if(hasSlipperySurface() == true)
	 *		|	then result == true
	 *		Otherwise, returns false
	 *		|else result == false
	 */
	@Raw
	public boolean isSlippery(){
		if((getHumidity() == 100) && (getTemperature().toCelsius().compareTo(Temperature.CELSIUS_0) > 0))
			return true;
		if((getHumidity() >= 10) && (getTemperature().toCelsius().compareTo(Temperature.CELSIUS_0) <= 0))
			return true;
		if(hasSlipperySurface() == true)
			return true;
		return false;
	}
	
	/**
	 * Variable registering whether or not the square has a slippery surface.
	 */
	private boolean slipperySurface = false;

	
	/**
	 * Returns you the current degree of inhabitibility
	 * of this square
	 * 
	 * @return The degree of inhabitibility
	 * 		|-1*(Math.sqrt(Math.pow(getHeatDamage(),3)/(101-getHumidity())))
	 * 		|	 - Math.sqrt(getColdDamage())
	 */
	@Raw
	public double getInhabitability(){
		double degreeOfInhabitability = -1*( Math.sqrt(Math.pow(getHeatDamage(), 3)/(101-getHumidity()))) - Math.sqrt(getColdDamage());
		return degreeOfInhabitability;
	}
	

	/**
	 * Sets the static constant defining how much influence dry Squares have
	 * in temperature calculations. Has to be between 0.1 and 0.4, else an
	 * IllegalArgumentException is thrown.
	 * 
	 * @param newConstant
	 * 		The new static constant to be set
	 * @post The TemperatureMergingConstant is set to newConstant
	 * 		|newConstant == Square.TEMPERATURE_MERGING_CONSTANT
	 * @throws IllegalArgumentException
	 * 		This constant needs to be between 0.1 and 0.4
	 * 		|(newConstant < 0.1) || (newConstant > 0.4))
	 */
	@Raw
	public static void setTemperatureMergingConstant(double newConstant) throws IllegalArgumentException{
		if( (newConstant < 0.1) || (newConstant > 0.4))
			throw new IllegalArgumentException("newConstant out of bounds");
		TEMPERATURE_MERGING_CONSTANT = newConstant;
	}

	/**
	 * Returns the Temperature Merging Constant used when weighted average
	 * temperatures are calculated for multiple squares. This constant defines
	 * how much influence dry squares have on this calculation
	 * 
	 * @return The temperature merging constant
	 */
	@Basic
	public static double getTemperatureMergingConstant() {
		return TEMPERATURE_MERGING_CONSTANT;
	}
	
	/**
	 * The Default merging constant for dry squares (0.2)
	 */
	private static double TEMPERATURE_MERGING_CONSTANT = 0.2d;
	
	
	/**
	 * Checks whether or not the current border can be upgraded to
	 * a new one.
	 * A wall upgrades over doors and null; doors over null.
	 * If the borders are equal true will be returned.
	 * 
	 * Closed doors are upgrades over open doors.
	 * 
	 * @param border
	 * 			The border to compare to
	 * @param direction
	 * 			The direction in which to compare
	 * @return whether or not the new border is equal or an upgrade
	 * 			|if(border == null){
	 *			|	if(getBorder(direction) == null)
	 *			|		result == true;
	 *			|	result == false;
	 *			|}
	 *			|if(getBorder(direction) == null)
	 *			|	result == true;
	 *			|if(border instanceof Door){
	 *			|	if(getBorder(direction) instanceof Door)
	 *			|		result == (border.isObstacle() || !getBorder(direction).isObstacle());
	 *			|	result == false;
	 *			|}
	 *			|if(getBorder(direction) instanceof Door)
	 *			|	result == true;
	 *			|result == true;
	 */
	public boolean isBorderEqualOrUpgrade(Border border, Direction direction){
		if(border == null){
			if(getBorder(direction) == null)
				return true;
			return false;
		}
		if(getBorder(direction) == null)
			return true;
		if(border instanceof Door){
			if(getBorder(direction) instanceof Door)
				return (border.isObstacle() || !getBorder(direction).isObstacle());
			return false;
		}
		if(getBorder(direction) instanceof Door)
			return true;
		return true;
	}
	
	/**
	 * Upgrades a border to the new one, but only if it's an upgrade
	 * or equal.
	 * 
	 * Does nothing if isBorderEqualOrUpgrade(border, direction)
	 * returns false.
	 * 
	 * @param border
	 * 			The new border
	 * @param direction
	 * 			The new direction in which to put this border
	 */
	@Raw
	public void upgradeBorderTo(Border border, Direction direction){
		if(isBorderEqualOrUpgrade(border, direction)){
			setBorder(border, direction);
		}
	}
	
	@Override
	public String toString(){
		String string = "";
		String className = ""+ this.getClass();
		className = className.substring(className.lastIndexOf(".")+1);
		string += "======"+ className+ " state======\n";
		string += "Temperature: "+ getTemperature().toString() + "\n";
		string += "Humidity: " + formatHumidityPercentage(getHumidity());
		return string;
	}
	
}
